namespace ScrumSprintMonitor.Implementation
{
    #region #using Directives

    using System;
    using System.Diagnostics;
    using System.Threading;

    using ContinuousLinq.Reactive;

    using Infrastructure;

    using PreEmptive.Attributes;

    #endregion

    public abstract class DataStreamObjectSerializer : ReactiveObject,
                                                       IDisposable
    {
        protected readonly IDataStreamProvider streamProvider;
        private bool disposed;
        protected bool initialized;
        private SynchronizationContext syncContext;

        protected DataStreamObjectSerializer(IDataStreamProvider streamProvider)
        {
            if (streamProvider == null)
            {
                throw new ArgumentNullException("streamProvider");
            }

            this.streamProvider = streamProvider;
        }

        protected bool Disposed
        {
            [DebuggerStepThrough]
            get { return this.disposed; }
        }

        ~DataStreamObjectSerializer()
        {
            Dispose(false);
        }

        protected void EnsureObjectIsRead()
        {
            if (!this.initialized)
            {
                this.streamProvider.StreamChanged += OnStreamProviderStreamChanged;

                try
                {
                    ReadObjectFromStream();
                }
                finally
                {
                    this.syncContext = SynchronizationContext.Current;
                    this.initialized = true;
                }
            }
        }

        protected void GuardDisposed()
        {
            if (Disposed)
            {
                throw new ObjectDisposedException(GetType().Name);
            }
        }

        protected abstract void ReadObjectFromStream();

        protected void FreezeStreamChangeNotifications()
        {
            this.streamProvider.StreamChanged -= OnStreamProviderStreamChanged;
        }

        protected void UnfreezeStreamChangeNotifications()
        {
            this.streamProvider.StreamChanged += OnStreamProviderStreamChanged;
        }

        [Feature("Configuration file change")]
        private void OnStreamProviderStreamChanged(object sender, EventArgs e)
        {
            ReadObjectFromStream();
            this.initialized = true;
        }

        #region Implementation of IDisposable

        /// <summary>
        ///   Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            GC.SuppressFinalize(this);
            Dispose(true);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                this.disposed = true;
                this.streamProvider.StreamChanged -= OnStreamProviderStreamChanged;

                var disposable = this.streamProvider as IDisposable;
                if (disposable != null)
                {
                    disposable.Dispose();
                }
            }
        }

        #endregion

        #region Implementation of INotifyPropertyChanged

        private void RaisePropertyChanged(object state)
        {
            OnPropertyChanged((string) state);
        }

        protected void AsyncRaisePropertyChanged(string propertyName)
        {
            if (!this.initialized)
            {
                return;
            }

            if (this.syncContext == null || !Thread.CurrentThread.IsBackground)
            {
                OnPropertyChanged(propertyName);
            }
            else
            {
                this.syncContext.Post(RaisePropertyChanged, propertyName);
            }
        }

        #endregion
    }
}